package networks.client;

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import networks.event.Delegate;
import networks.event.ManualResetEvent;
import networks.protocol.Message;
import networks.protocol.MessageType;
import networks.protocol.Protocol;

public class SocketClient {

    public Delegate.ActionOne<NetWorkState> NetWorkStateChangedEvent;

    private NetWorkState _netWorkState = NetWorkState.CLOSED; // current network state

    private EventManager _eventManager;
    private Socket _socket;
    private Protocol _protocol;
    private boolean _disposed = false;
    private byte _requestId = 1;

    private ManualResetEvent _timeoutEvent = new ManualResetEvent(false);
    private int timeoutMSec = 8000; // connect timeout count in millisecond

    public void InitClient(String host, int port, Delegate.Action callback) {
        _timeoutEvent.Reset();
        _eventManager = new EventManager();
        netWorkChanged(NetWorkState.CONNECTING);

        try {
            _socket = new Socket(host, port);
            _protocol = new Protocol(this, _socket);
            netWorkChanged(NetWorkState.CONNECTED);
            callback.Invoke();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
            if (_netWorkState != NetWorkState.TIMEOUT) {
                netWorkChanged(NetWorkState.ERROR);
            }
            Dispose();
        } finally {
            _timeoutEvent.Set();
        }

        try {
            if (_timeoutEvent.WaitOne((long) timeoutMSec)) {
                if (_netWorkState != NetWorkState.CONNECTED && _netWorkState != NetWorkState.ERROR) {
                    netWorkChanged(NetWorkState.TIMEOUT);
                    Dispose();
                }
            }
        } catch (Exception e) {
            // TODO: handle exception
        }
    }

    private void netWorkChanged(NetWorkState state) {
        _netWorkState = state;
        NetWorkStateChangedEvent.Invoke(state);
    }

    // -------------------------------------------------------------------------------
    // connect
    // -------------------------------------------------------------------------------
    public void Connect() {
        Connect(null);
    }

    public boolean Connect(Delegate.Action handshakeCallback) {
        try {
            _protocol.Start(handshakeCallback);
            return true;
        } catch (Exception e) {
            System.out.println("Connect Exception");
            e.printStackTrace();
            return false;
        }
    }

    // -------------------------------------------------------------------------------
    // request
    // -------------------------------------------------------------------------------
    public void Request(String route, Delegate.ActionOne<Message> action) {
        Request(route, null, action);
    }

    public void Request(String route, Object msg, Delegate.ActionOne<Message> action) {
        _eventManager.AddCallBack(_requestId, action);
        _protocol.Send(route, _requestId, msg);

        idAutoIncrement();
    }

    private void idAutoIncrement() {
        if (_requestId >= 255) {
            _requestId = 1;
        } else {
            _requestId++;
        }
    }

    // -------------------------------------------------------------------------------
    // notify
    // -------------------------------------------------------------------------------
    public void Notify(String route, Object msg) {
        _protocol.Send(route, msg);
    }

    // -------------------------------------------------------------------------------
    // receive broadcast
    // -------------------------------------------------------------------------------
    public void On(String eventName, Delegate.ActionOne<Message> action) {
        _eventManager.AddOnEvent(eventName, action);
    }

    // -------------------------------------------------------------------------------
    // processMessage
    // -------------------------------------------------------------------------------
    public void ProcessMessage(Message msg) {
        if (msg.type == MessageType.MSG_RESPONSE) {
            _eventManager.InvokeCallBack(msg.id, msg);
        } else if (msg.type == MessageType.MSG_PUSH) {
            _eventManager.InvokeOnEvent(msg.route, msg);
        }
    }

    // -------------------------------------------------------------------------------
    // disconnect
    // -------------------------------------------------------------------------------
    public void Disconnect() {
        Dispose();
        netWorkChanged(NetWorkState.DISCONNECTED);
    }

    public void Dispose() {
        dispose(true);
    }

    // The bulk of the clean-up code
    private void dispose(Boolean disposing) {
        if (_disposed) {
            return;
        }

        if (disposing) {
            // free managed resources
            _protocol.Close();
            _eventManager.Dispose();

            try {
                _socket.shutdownInput();
                _socket.shutdownOutput();
                _socket.close();
                _socket = null;
            } catch (Exception e) {
                // TODO : 有待确定这里是否会出现异常
            }

            _disposed = true;
        }
    }

}
